package edu.umd.ion.wavemeter.server;

import java.util.Date;
import java.util.Iterator;
import java.util.List;
import java.util.TreeMap;
import java.util.Vector;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.PersistenceException;
import javax.persistence.Query;

import edu.umd.ion.wavemeter.service.CalibrationResult;
import edu.umd.ion.wavemeter.service.CalibrationSettings;
import edu.umd.ion.wavemeter.service.ChannelInfo;
import edu.umd.ion.wavemeter.service.DynamicExposureParameters;
import edu.umd.ion.wavemeter.service.InterlockSettings;
import edu.umd.ion.wavemeter.service.PIDSettings;

public class DataBackend {
	private EntityManagerFactory emf;
	private TreeMap<String,String> ClientSettingsCache;
	private TreeMap<Integer,PersistentInterlockSettings> InterlockSettingsCache;
	private PersistentDynamicExposureParameters ExposureParametersCache;
	private TreeMap<Integer,PersistentPIDSettings> PIDSettingsCache;
	private TreeMap<Integer,PersistentChannelInfo> ChannelInfoCache;
	private Vector<CalibrationResult> CalibrationHistory;
	private TreeMap<Integer, PersistentCalibrationSettings> CalibrationSettingsCache;
	private ExecutorService MyWorkQueue = Executors.newSingleThreadExecutor();
	
	@SuppressWarnings("unchecked")
	public DataBackend() {
		emf =  WavemeterConfiguration.createEntityManagerFactory();
		EntityManager em = emf.createEntityManager();
		
		{
			Query q = em.createNamedQuery("Usernames");
			Iterator<PersistentDisplaySettings> I = q.getResultList().iterator();
			ClientSettingsCache = new TreeMap<String,String>();
			while (I.hasNext()) {
				PersistentDisplaySettings T = I.next();
				ClientSettingsCache.put(T.getUsername(),T.getSettings());
			}
		}
		{
			Query q = em.createNamedQuery("AllInterlockSettings");
			Iterator<PersistentInterlockSettings> I = q.getResultList().iterator();
			InterlockSettingsCache = new TreeMap<Integer,PersistentInterlockSettings>();
			while (I.hasNext()) {
				PersistentInterlockSettings C = I.next();
				InterlockSettingsCache.put(C.getChannel(), C);
			}
		}
		{
			Query q = em.createNamedQuery("AllCalibrationSettings");
			Iterator<PersistentCalibrationSettings> I = q.getResultList().iterator();
			CalibrationSettingsCache = new TreeMap<Integer,PersistentCalibrationSettings>();
			while (I.hasNext()) {
				PersistentCalibrationSettings C = I.next();
				CalibrationSettingsCache.put(C.getChannel(), C);
			}
		}
		{
			Query q = em.createNamedQuery("AllDynamicExposureParams");
			Iterator<PersistentDynamicExposureParameters> I = q.getResultList().iterator();
			if (I.hasNext()) {
				ExposureParametersCache = I.next();
			} else {
				ExposureParametersCache = new PersistentDynamicExposureParameters();
			}		
		}
		{
			Query q = em.createNamedQuery("AllPIDSettings");
			Iterator<PersistentPIDSettings> I = q.getResultList().iterator();
			PIDSettingsCache = new TreeMap<Integer, PersistentPIDSettings>();
			while (I.hasNext()) {
				PersistentPIDSettings C = I.next();
				PIDSettingsCache.put(C.getChannel(), C);
			}
		}
		{
			Query q = em.createNamedQuery("AllChannelInfo");
			Iterator<PersistentChannelInfo> I = q.getResultList().iterator();
			ChannelInfoCache = new TreeMap<Integer, PersistentChannelInfo>();
			while (I.hasNext()) {
				PersistentChannelInfo C = I.next();
				ChannelInfoCache.put(C.getChannel(), C);
			}
		}
		{
			CalibrationHistory = new Vector<CalibrationResult>(); 
			Query q = em.createNamedQuery("RelevantCalibrations");
			q.setMaxResults(10); 
			List<PersistentCalibrationResult> results = q.getResultList();
			java.util.Iterator<PersistentCalibrationResult> I = results.iterator();
			while (I.hasNext() ) {
				CalibrationHistory.add(I.next().plain());
			}
		}
		em.close();
	}
	
	private class SetTask<T extends PersistentObject<?,T>> implements Runnable {
		T Data;
		Class<T> Cls;
		public SetTask(Class<T> cls, T data) {
			Data = data;
			Cls = cls;
		}
		@Override
		public void run() {
			synchronized(emf) {
				EntityManager em = emf.createEntityManager();
				em.getTransaction().begin();
				em.merge(Data);
				em.getTransaction().commit();
				em.close();
			}	
		}
		
	}
	
	synchronized public void setSettings(String username, String settings) throws PersistenceException {
		PersistentDisplaySettings client = new PersistentDisplaySettings();
		client.setUsername( username );
		client.setSettings( settings );
		ClientSettingsCache.put(username, settings);
		MyWorkQueue.execute(new SetTask<PersistentDisplaySettings>(PersistentDisplaySettings.class, client));
	}

	synchronized public String getSettings(String username) {
		return ClientSettingsCache.get(username);
	}

	synchronized public String[] getUsernames() {
		return ClientSettingsCache.keySet().toArray(new String[ClientSettingsCache.size()]);
	}
	
	synchronized void setInterlockSettings(InterlockSettings settings) throws PersistenceException {
		PersistentInterlockSettings Settings = new PersistentInterlockSettings();
		Settings.set(settings);
		InterlockSettingsCache.put(settings.Channel, Settings);
		MyWorkQueue.execute(new SetTask<PersistentInterlockSettings>(PersistentInterlockSettings.class, Settings));
	}
	
	synchronized InterlockSettings getInterlockSettings(int virtchannel) {
		PersistentInterlockSettings cached =InterlockSettingsCache.get(virtchannel);
		if (cached==null) {
			cached =  new PersistentInterlockSettings();
			cached.setChannel( virtchannel );
		}
		return cached.plain();
	}
	
	synchronized void setCalibrationSettings(CalibrationSettings settings) throws PersistenceException {
		PersistentCalibrationSettings Settings = new PersistentCalibrationSettings();
		Settings.set(settings);
		CalibrationSettingsCache.put(settings.Channel, Settings);
		MyWorkQueue.execute(new SetTask<PersistentCalibrationSettings>(PersistentCalibrationSettings.class, Settings));
	}
	
	synchronized CalibrationSettings getCalibrationSettings(int virtchannel) {
		PersistentCalibrationSettings cached =CalibrationSettingsCache.get(virtchannel);
		if (cached==null) {
			cached =  new PersistentCalibrationSettings();
			cached.setChannel( virtchannel );
		}
		return cached.plain();
	}
	
	synchronized DynamicExposureParameters getExposureParameters() {
		return ExposureParametersCache.plain();
	}
	
	synchronized void setExposureParameters(DynamicExposureParameters p) throws PersistenceException {
		PersistentDynamicExposureParameters Settings = new PersistentDynamicExposureParameters();
		Settings.set(p);
		if (Settings.getUpdateDate()==null) {
			Settings.setUpdateDate( new Date( System.currentTimeMillis() ));
		}
		ExposureParametersCache = Settings;
		MyWorkQueue.execute(new SetTask<PersistentDynamicExposureParameters>(PersistentDynamicExposureParameters.class, Settings));
	}
	
	synchronized void setChannelInfo(ChannelInfo Info) throws PersistenceException {
		PersistentChannelInfo PInfo = new PersistentChannelInfo();
		PInfo.set(Info);
		ChannelInfoCache.put(Info.Channel, PInfo);
		MyWorkQueue.execute(new SetTask<PersistentChannelInfo>(PersistentChannelInfo.class, PInfo));
	}

	synchronized void setChannelInfo(int Channel, String Name) throws PersistenceException {
		PersistentChannelInfo Info = new PersistentChannelInfo();
		Info.setChannel(Channel);
		Info.setName(Name);
		Info.setLastSeen(new Date(System.currentTimeMillis()));
		ChannelInfoCache.put(Channel, Info);
		MyWorkQueue.execute(new SetTask<PersistentChannelInfo>(PersistentChannelInfo.class,Info));
	}
 	
	synchronized ChannelInfo getChannelInfo(int Channel) {
		ChannelInfo r = ChannelInfoCache.get(Channel).plain();
		if (r==null) {
			r = new ChannelInfo();
			r.Channel = Channel;
		}
		return r;
	}

	synchronized ChannelInfo[] getChannelInfo() {
		ChannelInfo[] r = new ChannelInfo[ChannelInfoCache.size()];
		Iterator<PersistentChannelInfo> I = ChannelInfoCache.values().iterator();
		for(int i=0; I.hasNext(); ++i ) {
			r[i] = I.next().plain();
		}
		return r;
	}
	
	synchronized void setPIDSettings(PIDSettings PID) throws PersistenceException {
		PersistentPIDSettings ppid = new PersistentPIDSettings();
		ppid.set(PID);
		PIDSettingsCache.put(PID.Channel, ppid);
		MyWorkQueue.execute(new SetTask<PersistentPIDSettings>(PersistentPIDSettings.class, ppid));
	}
	
	synchronized PIDSettings getPIDSettings(int Channel) {
		PersistentPIDSettings r = PIDSettingsCache.get(Channel);
		if (r==null) {
			r = new PersistentPIDSettings();
			r.Channel = Channel;
		}
		return r.plain();
	}
	
	private class LogCalibration implements Runnable {
		CalibrationResult Calibration;
		public LogCalibration(CalibrationResult calibration) {
			Calibration = calibration;
		}
		public void run() {
			synchronized(emf) {
				EntityManager em = emf.createEntityManager();
				PersistentCalibrationResult Persist = new PersistentCalibrationResult();
				Persist.set(Calibration);
				em.getTransaction().begin();
				em.persist(Persist);
				em.getTransaction().commit();
				em.close();
			}	
		}
	}

	synchronized public void addCalibration(CalibrationResult cal) {
		CalibrationHistory.add(0,cal);
		MyWorkQueue.execute( new LogCalibration(cal) );
	}
	
	synchronized public CalibrationResult[] getCalibrationHistory() {
		return CalibrationHistory.toArray(new CalibrationResult[CalibrationHistory.size()]);
	}
}
